/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

// Fork of https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js

import { Schema } from "prosemirror-model";

const pDOM = ["p", 0];
const blockquoteDOM = ["blockquote", 0];
const hrDOM = ["hr"];
const preDOM = ["pre", ["code", 0]];
const brDOM = ["br"];

// :: Object
// [Specs](#model.NodeSpec) for the nodes defined in this schema.
export const nodes = {
	// :: NodeSpec The top level document node.
	doc: {
		// Making one block to start for simplicity
		content: "block+",
	},

	// :: NodeSpec A plain paragraph textblock. Represented in the DOM
	// as a `<p>` element.
	paragraph: {
		content: "inline*",
		group: "block",
		parseDOM: [{ tag: "p" }],
		toDOM: () => pDOM,
	},

	// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
	blockquote: {
		content: "block+",
		group: "block",
		defining: true,
		parseDOM: [{ tag: "blockquote" }],
		toDOM: () => blockquoteDOM,
	},

	// :: NodeSpec A horizontal rule (`<hr>`).
	horizontal_rule: {
		group: "block",
		parseDOM: [{ tag: "hr" }],
		toDOM: () => hrDOM,
	},

	// :: NodeSpec A heading textblock, with a `level` attribute that
	// should hold the number 1 to 6. Parsed and serialized as `<h1>` to
	// `<h6>` elements.
	heading: {
		attrs: { level: { default: 1 } },
		content: "inline*",
		group: "block",
		defining: true,
		parseDOM: [
			{ tag: "h1", attrs: { level: 1 } },
			{ tag: "h2", attrs: { level: 2 } },
			{ tag: "h3", attrs: { level: 3 } },
			{ tag: "h4", attrs: { level: 4 } },
			{ tag: "h5", attrs: { level: 5 } },
			{ tag: "h6", attrs: { level: 6 } },
		],
		toDOM: (node) => [`h${node.attrs.level}`, 0],
	},

	// :: NodeSpec A code listing. Disallows marks or non-text inline
	// nodes by default. Represented as a `<pre>` element with a
	// `<code>` element inside of it.
	code_block: {
		content: "text*",
		marks: "",
		group: "block",
		code: true,
		defining: true,
		parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
		toDOM: () => preDOM,
	},

	// :: NodeSpec The text node.
	text: {
		group: "inline",
	},

	// :: NodeSpec An inline image (`<img>`) node. Supports `src`,
	// `alt`, and `href` attributes. The latter two default to the empty
	// string.
	fluid: {
		group: "inline",
		content: "inline*",
		inline: true,
		draggable: true,
		// This makes the view treat the node as a leaf, even though it
		// technically has content
		atom: true,
		toDOM(node) {
			const { src, alt, title } = node.attrs;
			return ["fluid", { src, alt, title }];
		},
		parseDOM: [
			{
				tag: "fluid",
				getAttrs: (dom) => ({
					alt: dom.getAttribute("alt"),
					src: dom.getAttribute("src"),
					title: dom.getAttribute("title"),
				}),
			},
		],
		attrs: {
			src: { default: "" },
			alt: { default: null },
			title: { default: null },
		},
	},

	footnote: {
		group: "inline",
		content: "inline*",
		inline: true,
		draggable: true,
		// This makes the view treat the node as a leaf, even though it
		// technically has content
		atom: true,
		toDOM: () => ["footnote", 0],
		parseDOM: [{ tag: "footnote" }],
	},

	// :: NodeSpec An inline image (`<img>`) node. Supports `src`,
	// `alt`, and `href` attributes. The latter two default to the empty
	// string.
	image: {
		inline: true,
		attrs: {
			src: {},
			alt: { default: null },
			title: { default: null },
		},
		group: "inline",
		draggable: true,
		parseDOM: [
			{
				tag: "img[src]",
				getAttrs: (dom) => ({
					src: dom.getAttribute("src"),
					title: dom.getAttribute("title"),
					alt: dom.getAttribute("alt"),
				}),
			},
		],
		toDOM(node) {
			const { src, alt, title } = node.attrs;
			return ["img", { src, alt, title }];
		},
	},

	// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
	hard_break: {
		inline: true,
		group: "inline",
		selectable: false,
		parseDOM: [{ tag: "br" }],
		toDOM: () => brDOM,
	},
};

const emDOM = ["em", 0];
const strongDOM = ["strong", 0];
const codeDOM = ["code", 0];

// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks = {
	// :: MarkSpec A link. Has `href` and `title` attributes. `title`
	// defaults to the empty string. Rendered and parsed as an `<a>`
	// element.
	link: {
		attrs: {
			href: {},
			title: { default: null },
		},
		inclusive: false,
		parseDOM: [
			{
				tag: "a[href]",
				getAttrs: (dom) => ({
					href: dom.getAttribute("href"),
					title: dom.getAttribute("title"),
				}),
			},
		],
		toDOM(node) {
			const { href, title } = node.attrs;
			return ["a", { href, title }, 0];
		},
	},

	// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
	// Has parse rules that also match `<i>` and `font-style: italic`.
	em: {
		parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
		toDOM: () => emDOM,
	},

	// :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
	// also match `<b>` and `font-weight: bold`.
	strong: {
		parseDOM: [
			{ tag: "strong" },
			// This works around a Google Docs misbehavior where
			// pasted content will be inexplicably wrapped in `<b>`
			// tags with a font-weight normal.
			{ tag: "b", getAttrs: (node) => node.style.fontWeight !== "normal" && null },
			{
				style: "font-weight",
				getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
			},
		],
		toDOM: () => strongDOM,
	},

	// :: MarkSpec Code font mark. Represented as a `<code>` element.
	code: {
		parseDOM: [{ tag: "code" }],
		toDOM: () => codeDOM,
	},
};

// :: Schema
// This schema rougly corresponds to the document schema used by
// [CommonMark](http://commonmark.org/), minus the list elements,
// which are defined in the [`prosemirror-schema-list`](#schema-list)
// module.
//
// To reuse elements from this schema, extend or read from its
// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
export const schema = new Schema({ nodes: nodes as any, marks: marks as any });
